每一个人都应当了解的科学技术(Techniques Everyone Should Know)
由 Peter Bailis 介绍
被选中的读物
在这个篇章之中,我们将会就数据库系统设计当中主要的,以及近主要的核心概念的策源地文章进行介绍:查询规划,事务控制,数据库恢复,以及分布式。而本章当中所牵涉到的思想,几乎是每一款现代数据库系统的基础,而每一款成熟的数据库系统基本上都牵涉到了这些概念。本章当中所搜集的三篇参考文章都是他们所立足的各自领域的经典文章。除此之外,与(红皮书)前面的篇章不同,在本章里面,我们更倾向 于聚焦那些已经得到广泛应用的科学技术与程序算法,而非整个数据库系统。
查询优化(Query Optimization)
在关系型数据库领域里面,查询优化技术至关重要,它是实现独立于数据的查询处理的核心支撑(即查询效率的好坏与数据关联度小,译者注)。Selinger 等人围绕 System R 的研究而著成的奠基性文章,通过将查询优化这个大问题,划分为三个小问题:代价的估计(cost estimation),通过关系的等价性(relational equivalences)定义搜索空间,以及基于代价的搜索(cost-based search),来让查询优化落地生根。
优化器会为查询执行当中的所涉及到的每一个组件,提供对应的代价估计,其测量单位,使用I/O与CPU的代价(I/O and CPU costs)来进行表示。而为了顺利实现这一目的,优化器,一方面需要依靠由我们预先设计出的有关每条关系数据存储内容的统计信息(这些数据存储于系统目录,即 system catalog 中),而另一方面,(优化器)需要结合一组启发式方法,来决定查询输出的基数大小(比如,基于预估的谓词选择性(predicate selectivity))。而作为练习,我们可以对这些启发式的方法,做一个详细地考虑:究竟何时,使用他们才算明智?究竟何时,哪一种输入将会导致他们工作失误?又或者,他们如何才可以更好地展开工作?
依托这些代价估计参数,优化器使用动态编程算法为查询构建出对应的执行计划。优化器 ,将会定义出一组物理操作符,它们是对逻辑操作符的具体实现(比如,查找一则元组,就使用完整的“段”扫描,和索引来完成)。而依托于这组操作符集合,优化器即可以迭代地构造出一棵“左深”操作符树来,这棵树使用代价启发式策略让总体运行这些操作符的工作量,达到一个最低水平,进而兼顾上游消费者所需的“感兴趣的顺序”。
这种做法同样规避了在运算时需要计算出操作符全部排列组合顺序的需要。然而,在查询规划这件事情的开销上,它依旧是一个指数级别的操作。正如我们将在第七章讨论的情况那样,现代的查询优化器,依旧难以处理庞大的查询计划(比如,多路 join)。除此之外,即便 Selinger 等人的优化器,会在执行之前,做一个预编译的工作,但是其它很多早期的如Ingres[^25]那样的数据库系统,实际上依旧在逐个元组的基础上面展开工作。
就像几乎所有的查询优化器那样,Selinger 他们的查询优化器,实际上也不是“最优的” --- 它们并不保证由查询优化器最终所选择的查询计划,将会是最快速,或者是成本最廉价的。关系型数据库的查询优化器,从精神上面看,更加接近于现代语言编译器当中的代码优化例程(比如,将要执行尽力而为的搜索行为(best-effort search)),而并非数学意义上面的优化例程(比如,将要找出最优化的解决方案)。不过,如今许多关系型数据库引擎,都沿用了本文当中的方法,包括二进制操作符,以及基于代价的成本估计。